// $Id: module.cpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#define XPCOM_SOURCE

#include <xpcom/config.hpp>
#include <xpcom/array.hpp>
#include <xpcom/assert.hpp>
#include <xpcom/memory.hpp>
#include <xpcom/module.hpp>
#include <xpcom/ptr.hpp>
#include <xpcom/impl/array.hpp>
#include <xpcom/impl/exception.hpp>
#include <xpcom/impl/uuid_ptr.hpp>
#include <xpcom/proxy/pstring.hpp>
#include <xpcom/proxy/ptr.hpp>
#include "nsIClassInfo.hpp"
#include "nsIComponentManager.hpp"
#include "nsIComponentRegistrar.hpp"
#include "nsIFactory.hpp"
#include "nsIModule.hpp"
#include "nsIProgrammingLanguage.hpp"

namespace xpcom {

// module_component_base
module_component_base::module_component_base() :
    classid_(uuid::nil),
    contractid_(),
    description_(),
    flags_(0),
    factory_(void_factory),
    iids_(0),
    iid_count_(0)
{
}

module_component_base::~module_component_base()
{
    if (iids_)
        memory::free(iids_);
}

module_base::module_base(uint32_t count) :
    version_(default_version),
    name_(),
    components_(0),
    component_count_(0)
{
    components_ = memory::alloc<const module_component_base*>(count);
    component_count_ = count;
}

module_base::~module_base()
{
    memory::free(components_);
}

namespace {

// factory_impl
class factory_impl :
    public object<factory_impl, nsIFactory, nsIClassInfo>
{
public:
    explicit factory_impl(const module_component_base* component) :
        component_(component)
    {
        XPCOM_ASSERT(component_);
    }

    // Allocate using XPCOM memory manager, may throw
    static void* operator new(std::size_t n)
    {
        return memory::alloc_data(n);
    }

    static void operator delete(void* p, std::size_t /*n*/)
    {
        memory::free_data(p);
    }

    // nsIFactory
    //XPCOM_IMETHOD CreateInstance(interface* outer, const uuid& iid, void** obj);
    XPCOM_IMETHOD LockFactory(bool_t lock);

    // nsIClassInfo
    XPCOM_IMETHOD GetInterfaces(uint32_t* count, uuid*** retval);
    XPCOM_IMETHOD GetHelperForLanguage(uint32_t language, interface** retval);
    XPCOM_IMETHOD GetContractID(char** retval);
    XPCOM_IMETHOD GetClassDescription(char** retval);
    XPCOM_IMETHOD GetClassID(uuid** retval);
    XPCOM_IMETHOD GetImplementationLanguage(uint32_t* retval);
    XPCOM_IMETHOD GetFlags(uint32_t* retval);
    XPCOM_IMETHOD GetClassIDNoAlloc(uuid* retval);

protected:
    const module_component_base* component_;
};

XPCOM_IMETHODIMP factory_impl::LockFactory(bool_t /*lock*/)
{
    // XXX do we care if (info->flags_ & flag_threadsafe)?
    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetInterfaces(uint32_t* count, uuid*** retval)
{
    if (XPCOM_UNLIKELY(!count || !retval))
        return NS_ERROR_NULL_POINTER;

    if (!component_->iids_) {
        *count = 0;
        *retval = 0;
        return NS_OK;
    }

    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        impl::array_out2<uuid> val(retval, count);
        static_cast<array<uuid>&>(val).assign(
                component_->iids_, component_->iids_ + component_->iid_count_);
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetHelperForLanguage(
        uint32_t /*language*/, interface** retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    *retval = 0;
    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetContractID(char** retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        *retval = component_->contractid_.clone_data();
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetClassDescription(char** retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        *retval = component_->description_.clone_data();
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetClassID(uuid** retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        impl::uuid_ptr_out val(retval);
        val = component_->classid_;
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetImplementationLanguage(uint32_t* retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    *retval = nsIProgrammingLanguage::CPLUSPLUS;
    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetFlags(uint32_t* retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    *retval = component_->flags_;
    return NS_OK;
}

XPCOM_IMETHODIMP factory_impl::GetClassIDNoAlloc(uuid* retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    *retval = component_->classid_;
    return NS_OK;
}

// standard_factory_impl
class standard_factory_impl : public factory_impl
{
public:
    explicit standard_factory_impl(const module_component_base* component) :
        factory_impl(component)
    {
    }

    XPCOM_IMETHOD CreateInstance(interface* outer, const uuid& iid, void** obj);
};

XPCOM_IMETHODIMP standard_factory_impl::CreateInstance(
        interface* outer, const uuid& iid, void** obj)
{
    if (XPCOM_UNLIKELY(!obj))
        return NS_ERROR_NULL_POINTER;
    *obj = 0;

    // No aggregation
    if (outer) {
        return NS_ERROR_NO_AGGREGATION;
    }

    interface* p = 0;
    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        XPCOM_ASSERT(component_->factory_ !=
                module_component_base::aggregatable_factory);
        p = static_cast<interface*>(component_->new_instance());
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    XPCOM_ASSERT(p);
    if (!p)
        return NS_ERROR_UNEXPECTED;

    p->add_ref();
    result_t r = p->query_interface(iid, obj);
    p->release();

    return r;
}

// aggregatable_factory_impl
class aggregatable_factory_impl : public factory_impl
{
public:
    explicit aggregatable_factory_impl(const module_component_base* component) :
        factory_impl(component)
    {
    }

    XPCOM_IMETHOD CreateInstance(interface* outer, const uuid& iid, void** obj);
};

XPCOM_IMETHODIMP aggregatable_factory_impl::CreateInstance(
        interface* outer, const uuid& iid, void** obj)
{
    if (XPCOM_UNLIKELY(!obj))
        return NS_ERROR_NULL_POINTER;
    *obj = 0;

    // Aggregation allows only root interface to be queried
    if (outer && iid != typeid_of<interface>()) {
        return NS_ERROR_NO_AGGREGATION;
    }

    aggregatable_interface* p = 0;
    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        XPCOM_ASSERT(component_->factory_ ==
                module_component_base::aggregatable_factory);
        p = static_cast<aggregatable_interface*>(
                static_cast<inner_interface*>(component_->new_instance()));
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    XPCOM_ASSERT(p);
    if (!p)
        return NS_ERROR_UNEXPECTED;

    if (outer) {
        p->outer(outer);
    }

    p->inner_add_ref();
    result_t r = p->inner_query_interface(iid, obj);
    p->inner_release();

    return r;
}

// singleton_factory_impl
class singleton_factory_impl : public factory_impl
{
public:
    explicit singleton_factory_impl(const module_component_base* component);
    virtual ~singleton_factory_impl();

    XPCOM_IMETHOD CreateInstance(interface* outer, const uuid& iid, void** obj);

private:
    interface* p_;
};

singleton_factory_impl::singleton_factory_impl(
        const module_component_base* component) :
    factory_impl(component),
    p_(0)
{
    // Make the instance when factory created, to avoid some problem
    XPCOM_ASSERT(component_->factory_ !=
            module_component_base::aggregatable_factory);
    p_ = static_cast<interface*>(component_->new_instance());

    XPCOM_ASSERT(p_);
    if (!p_)
        throw_exception(NS_ERROR_UNEXPECTED);

    p_->add_ref();
}

singleton_factory_impl::~singleton_factory_impl()
{
    if (p_)
        p_->release();
}

XPCOM_IMETHODIMP singleton_factory_impl::CreateInstance(
        interface* outer, const uuid& iid, void** obj)
{
    if (XPCOM_UNLIKELY(!obj))
        return NS_ERROR_NULL_POINTER;
    *obj = 0;

    // No aggregation
    if (outer) {
        return NS_ERROR_NO_AGGREGATION;
    }

    // Instance is already created, and have a reference
    XPCOM_ASSERT(p_);
    return p_->query_interface(iid, obj);
}

// module_impl
class module_impl :
    public object<module_impl, nsIModule>
{
public:
    module_impl(module_base* module, noref_ptr<nsIComponentManager> manager,
            noref_ptr<nsIFile> location);

    virtual ~module_impl();

    // Allocate using XPCOM memory manager, do not throw
    static void* operator new(std::size_t n)
    {
        return memory::alloc_data_nothrow(n);
    }

    static void operator delete(void* p, std::size_t /*n*/)
    {
        memory::free_data(p);
    }

    // nsIModule
    XPCOM_IMETHOD GetClassObject(
            nsIComponentManager* manager,
            const uuid& classid, const uuid& iid, void** obj);

    XPCOM_IMETHOD RegisterSelf(
            nsIComponentManager* manager,
            nsIFile* location, const char* loader_str, const char* type);

    XPCOM_IMETHOD UnregisterSelf(
            nsIComponentManager* manager,
            nsIFile* location, const char* loader_str);

    XPCOM_IMETHOD CanUnload(nsIComponentManager* manager, bool_t* retval);

private:
    result_t init(noref_ptr<nsIComponentManager> manager);
    void shutdown();

    static result_t new_factory(
            const module_component_base* component,
            ref_ptr<factory_impl>& factory);

    module_base* module_;
    bool initialized_;
};

module_impl::module_impl(
        module_base* module,
        noref_ptr<nsIComponentManager> manager,
        noref_ptr<nsIFile> /*location*/) :
    module_(module),
    initialized_(false)
{
    XPCOM_ASSERT(module_);
    XPCOM_ASSERT(module_->component_count_ == 0 || module_->components_);

    init(manager); // failure is ignored
}

module_impl::~module_impl()
{
    shutdown();
    try {
        module_->destroy();
    } catch (...) {
        // ignore
    }
}

result_t module_impl::init(noref_ptr<nsIComponentManager> manager)
{
    if (initialized_)
        return NS_OK;

    ref_ptr<nsIComponentRegistrar> registrar =
            dynamic_pointer_cast<nsIComponentRegistrar>(manager);
    if (!registrar)
        return NS_ERROR_FAILURE;

    // Eagerly create and register factory for component that has
    // eager_classinfo flag.
    for (uint32_t i = 0; i < module_->component_count_; ++i) {
        const module_component_base* component = module_->components_[i];

        if (component->flags_ & module_component_base::flag_eager_classinfo) {
            ref_ptr<factory_impl> factory;

            result r = new_factory(component, factory);
            if (r.failed())
                return r;

            // if we don't have a mConstructor, then we should not populate
            // the component manager.
            r = registrar->RegisterFactory(
                    component->classid_,
                    proxy::pstr_in(component->description_),
                    proxy::pstr_in(component->contractid_),
                    proxy::ptr_in(factory));

            if (r.failed())
                return r;
        }
    }

    // Call module's init
    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        module_->init(manager);
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    initialized_ = true;
    return NS_OK;
}

void module_impl::shutdown()
{
    if (initialized_) {
        try {
            module_->shutdown();
        } catch (...) {
            // ignore
        }

        initialized_ = false;
    }
}

result_t module_impl::new_factory(
        const module_component_base* component,
        ref_ptr<factory_impl>& factory)
{
    XPCOM_ASSERT(component);
    XPCOM_ASSERT(!factory);

    factory_impl* p = 0;
    XPCOM_TRANSLATE_EXCEPTION_BEGIN
    {
        switch (component->factory_) {
            case module_component_base::standard_factory:
                p = new standard_factory_impl(component);
                break;

            case module_component_base::aggregatable_factory:
                p = new aggregatable_factory_impl(component);
                break;

            case module_component_base::singleton_factory:
                p = new singleton_factory_impl(component);
                break;

            //case module_component_base::void_factory:
            default:
                return NS_ERROR_UNEXPECTED;
        }
    }
    XPCOM_TRANSLATE_EXCEPTION_END

    factory.reset(p);
    return NS_OK;
}

XPCOM_IMETHODIMP module_impl::GetClassObject(
        nsIComponentManager* manager,
        const uuid& classid, const uuid& iid, void** obj)
{
    if (XPCOM_UNLIKELY(!obj))
        return NS_ERROR_NULL_POINTER;
    *obj = 0;

    result r;

    // Do one-time-only initialization if necessary
    if (!initialized_) {
        r = init(manager);
        if (r.failed())
            return r;
    }

    for (uint32_t i = 0; i < module_->component_count_; ++i) {
        const module_component_base* component = module_->components_[i];
        if (component->classid_ == classid) {
            ref_ptr<factory_impl> factory;

            r = new_factory(component, factory);
            if (r.failed())
                return r;

            return factory.get()->query_interface(iid, obj);
        }
    }

    return NS_ERROR_FACTORY_NOT_REGISTERED;
}

XPCOM_IMETHODIMP module_impl::RegisterSelf(
        nsIComponentManager* manager,
        nsIFile* location, const char* loader_str, const char* type)
{
    ref_ptr<nsIComponentRegistrar> registrar =
            dynamic_pointer_cast<nsIComponentRegistrar>(
                    noref_ptr<nsIComponentManager>(manager));
    if (!registrar)
        return NS_ERROR_FAILURE;

    for (uint32_t i = 0; i < module_->component_count_; ++i) {
        const module_component_base* component = module_->components_[i];

        result r = registrar->RegisterFactoryLocation(
                component->classid_,
                proxy::pstr_in(component->description_),
                proxy::pstr_in(component->contractid_),
                location,
                loader_str,
                type);

        if (r.failed())
            return r;
    }

    return NS_OK;
}

XPCOM_IMETHODIMP module_impl::UnregisterSelf(
        nsIComponentManager* manager,
        nsIFile* location, const char* /*loader_str*/)
{
    ref_ptr<nsIComponentRegistrar> registrar =
            dynamic_pointer_cast<nsIComponentRegistrar>(
                    noref_ptr<nsIComponentManager>(manager));

    if (registrar) {
        for (uint32_t i = 0; i < module_->component_count_; ++i) {
            const module_component_base* component = module_->components_[i];
            registrar->UnregisterFactoryLocation(component->classid_, location);
            // XXX result is ignored
        }
    }

    return NS_OK;
}

XPCOM_IMETHODIMP module_impl::CanUnload(
        nsIComponentManager* manager, bool_t* retval)
{
    if (XPCOM_UNLIKELY(!retval))
        return NS_ERROR_NULL_POINTER;

    *retval = false_;
    return NS_ERROR_FAILURE;
}

struct module_guard
{
    module_guard(module_base* m) : m_(m)
    {
    }

    ~module_guard()
    {
        if (m_) {
            try { m_->destroy(); } catch (...) {}
        }
    }

    void release()
    {
        m_ = 0;
    }

    module_base* m_;
};

} // anonymous namespace

// new_module
XPCOM_METHOD new_module(
        module_base* module,
        nsIComponentManager* manager, nsIFile* location,
        nsIModule** retval) throw()
{
    module_guard g(module);

    if (XPCOM_UNLIKELY(!module || !retval))
        return NS_ERROR_NULL_POINTER;
    *retval = 0;

    // Checks the class id of each component of the module
    for (uint32_t i = 0; i < module->component_count_; ++i) {
        if (module->components_[i]->classid_.is_nil())
            return NS_ERROR_UNEXPECTED;
    }

    // Makes a new nsIModule implementation
    // (new module_impl does not throw)
    module_impl* impl = new module_impl(module, manager, location);
    if (!impl)
        return NS_ERROR_OUT_OF_MEMORY;

    // Release the guard here, the module will be take care by module_impl
    g.release();

    impl->add_ref();
    *retval = static_cast<nsIModule*>(impl);

    return NS_OK;
}

} // namespace xpcom
